﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace BeanboxSoftware.BeanProxy
{
  /// <summary>
  /// Defines the initial behavior for each method configuration on a new proxy
  /// </summary>
  public enum DefaultBehavior
  {
    Real,
    Fake,
    ThrowException,
  }

  /// <summary>
  /// Used to give detailed instructions when building a proxy.
  /// Note that when using a CtorConfig to build a proxy, the constructor
  /// parameters you want to use for the proxy's parent type's constructor
  /// have to be added to this object. Use Add
  /// </summary>
  public class CtorConfig
  {
    /// <summary>
    /// Defines the initial behavior for each method configuration on a new proxy
    /// The default value is ThrowException
    /// </summary>
    public DefaultBehavior DefaultBehavior { get; set; }
    
    private List<CtorArg> _args;

    /// <summary>
    /// Create a default construction configuration
    /// </summary>
    public CtorConfig()
      : this(DefaultBehavior.ThrowException)
    {
    }

    /// <summary>
    /// Create a construction configuration which will set the initial method behavior to
    /// defaultBehavior
    /// </summary>
    /// <param name="defaultBehavior">The default behavior used on all method configurations in a proxy</param>
    public CtorConfig(DefaultBehavior defaultBehavior)
    {
      DefaultBehavior = defaultBehavior;
      _args = new List<CtorArg>();
    }

    /// <summary>
    /// Add a constructor parameter
    /// </summary>
    /// <typeparam name="T">Parameter type</typeparam>
    /// <param name="arg">Parameter value</param>
    /// <returns>this, to simplify chaining Add calls</returns>
    public CtorConfig Add<T>(T arg)
    {
      return Add(typeof(T), arg);
    }

    /// <summary>
    /// Add a constructor parameter
    /// </summary>
    /// <param name="type">Parameter type</param>
    /// <param name="value">Parameter value</param>
    /// <returns>this, to simplify chaining Add calls</returns>
    public CtorConfig Add(Type type, object value)
    {
      _args.Add(new CtorArg() { Type = type, Value = value });
      return this;
    }

    /// <summary>
    /// Locate a best fit constructor
    /// </summary>
    /// <param name="type">Type to search for a constructor from</param>
    /// <returns>best fit constructor</returns>
    public ConstructorInfo GetCtor(Type type)
    {
      return type.GetConstructor(GetTypes());
    }

    /// <summary>
    /// Returns an array of the parameter values
    /// </summary>
    /// <returns>parameter values</returns>
    public object[] GetValues()
    {
      var list = new List<object>();

      foreach (CtorArg arg in _args)
      {
        list.Add(arg.Value);
      }

      return list.ToArray();
    }

    /// <summary>
    /// Returns an array of the parameter types
    /// </summary>
    /// <returns>parameter types</returns>
    public Type[] GetTypes()
    {
      var list = new List<Type>();

      foreach (CtorArg arg in _args)
      {
        list.Add(arg.Type);
      }

      return list.ToArray();
    }
  }
}
